package com.idanchuang.component.base.actuator;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
import org.springframework.boot.actuate.health.HealthWebEndpointResponseMapper;
import org.springframework.boot.actuate.health.ShowDetails;
import org.springframework.util.CollectionUtils;

import java.util.Set;
import java.util.function.Supplier;

/**
 * @author yjy
 * Created at 2021/3/19 11:00 上午
 */
public class DcHealthWebEndpointResponseMapper extends HealthWebEndpointResponseMapper {

    private final HealthStatusHttpMapper statusHttpMapper;

    private final Set<String> authorizedRoles;

    @Value("${management.endpoint.health.show-details:never}")
    private ShowDetails showDetails;

    public DcHealthWebEndpointResponseMapper(HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails, Set<String> authorizedRoles) {
        super(statusHttpMapper, showDetails, authorizedRoles);
        this.statusHttpMapper = statusHttpMapper;
        this.authorizedRoles = authorizedRoles;
    }


    /**
     * Maps the given {@code health} details to a {@link WebEndpointResponse}, honouring
     * the mapper's default {@link ShowDetails} using the given {@code securityContext}.
     * <p>
     * If the current user does not have the right to see the details, the
     * {@link Supplier} is not invoked and a 404 response is returned instead.
     * @param health the provider of health details, invoked if the current user has the
     * right to see them
     * @param securityContext the security context
     * @return the mapped response
     */
    @Override
    public WebEndpointResponse<Health> mapDetails(Supplier<Health> health,
                                                  SecurityContext securityContext) {
        if (canSeeDetails(securityContext, this.showDetails)) {
            Health healthDetails = health.get();
            if (healthDetails != null) {
                return createWebEndpointResponse(healthDetails);
            }
        }
        return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
    }

    /**
     * Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the
     * mapper's default {@link ShowDetails} using the given {@code securityContext}.
     * @param health the health to map
     * @param securityContext the security context
     * @return the mapped response
     */
    @Override
    public WebEndpointResponse<Health> map(Health health,
                                           SecurityContext securityContext) {
        return map(health, securityContext, this.showDetails);
    }

    /**
     * Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the given
     * {@code showDetails} using the given {@code securityContext}.
     * @param health the health to map
     * @param securityContext the security context
     * @param showDetails when to show details in the response
     * @return the mapped response
     */
    @Override
    public WebEndpointResponse<Health> map(Health health, SecurityContext securityContext,
                                           ShowDetails showDetails) {
        if (!canSeeDetails(securityContext, showDetails)) {
            health = Health.status(health.getStatus()).build();
        }
        return createWebEndpointResponse(health);
    }

    private WebEndpointResponse<Health> createWebEndpointResponse(Health health) {
        Integer status = this.statusHttpMapper.mapStatus(health.getStatus());
        return new WebEndpointResponse<>(health, status);
    }

    private boolean canSeeDetails(SecurityContext securityContext,
                                  ShowDetails showDetails) {
        if (showDetails == ShowDetails.NEVER
                || (showDetails == ShowDetails.WHEN_AUTHORIZED
                && (securityContext.getPrincipal() == null
                || !isUserInRole(securityContext)))) {
            return false;
        }
        return true;
    }

    private boolean isUserInRole(SecurityContext securityContext) {
        if (CollectionUtils.isEmpty(this.authorizedRoles)) {
            return true;
        }
        for (String role : this.authorizedRoles) {
            if (securityContext.isUserInRole(role)) {
                return true;
            }
        }
        return false;
    }


    public ShowDetails getShowDetails() {
        return showDetails;
    }

    public void setShowDetails(ShowDetails showDetails) {
        this.showDetails = showDetails;
    }
}
